﻿/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "Shared/Crypto/BigNumber.h"
#include <memory.h>
#include <algorithm>
#include <openssl/bn.h>
#include "Shared/Global_Macros.h"


namespace Shared {
namespace Crypto {

    BigNumber::BigNumber(void)
        : _bn(BN_new())
        , _array()
    {

    }

    BigNumber::BigNumber(const BigNumber& x)
        : _bn(BN_dup(x._bn))
        , _array()
    {

    }

    BigNumber::BigNumber(std::uint32_t x)
        : _bn(BN_new())
        , _array()
    {
        BN_set_word(_bn, x);
    }

    BigNumber::~BigNumber(void)
    {
        BN_free(_bn);
    }

    void BigNumber::setUInt32(std::uint32_t x)
    {
        BN_set_word(_bn, x);
    }

    void BigNumber::setUInt64(std::uint64_t x)
    {
        BN_add_word(_bn, (std::uint32_t)(x >> 32));
        BN_lshift(_bn, _bn, 32);
        BN_add_word(_bn, (std::uint32_t)(x & 0xFFFFFFFF));
    }

    void BigNumber::setBinary(const std::uint8_t* bytes, int len)
    {
        std::uint8_t t[1000];
        for (int i = 0; i < len; ++i) {
            t[i] = bytes[len - 1 - i];
        }
        BN_bin2bn(t, len, _bn);
    }

    void BigNumber::setHexStr(const char* str)
    {
        BN_hex2bn(&_bn, str);
    }

    void BigNumber::setRand(int numbits)
    {
        BN_rand(_bn, numbits, 0, 1);
    }

    void BigNumber::free(void* x)
    {
        OPENSSL_free(x);
    }

    BigNumber BigNumber::operator=(const BigNumber& x)
    {
        BN_copy(_bn, x._bn);
        return *this;
    }

    BigNumber BigNumber::operator+=(const BigNumber& x)
    {
        BN_add(_bn, _bn, x._bn);
        return *this;
    }

    BigNumber BigNumber::operator-=(const BigNumber& x)
    {
        BN_sub(_bn, _bn, x._bn);
        return *this;
    }

    BigNumber BigNumber::operator*=(const BigNumber& x)
    {
        BN_CTX* bnctx;

        bnctx = BN_CTX_new();
        BN_mul(_bn, _bn, x._bn, bnctx);
        BN_CTX_free(bnctx);

        return *this;
    }

    BigNumber BigNumber::operator/=(const BigNumber& x)
    {
        BN_CTX* bnctx;

        bnctx = BN_CTX_new();
        BN_div(_bn, NULL, _bn, x._bn, bnctx);
        BN_CTX_free(bnctx);

        return *this;
    }

    BigNumber BigNumber::operator%=(const BigNumber& x)
    {
        BN_CTX* bnctx;

        bnctx = BN_CTX_new();
        BN_mod(_bn, _bn, x._bn, bnctx);
        BN_CTX_free(bnctx);

        return *this;
    }

    BigNumber BigNumber::exp(const BigNumber& bn)
    {
        BigNumber ret;
        BN_CTX* bnctx;

        bnctx = BN_CTX_new();
        BN_exp(ret._bn, _bn, bn._bn, bnctx);
        BN_CTX_free(bnctx);

        return ret;
    }

    BigNumber BigNumber::modExp(const BigNumber& bn1, const BigNumber& bn2)
    {
        BigNumber ret;
        BN_CTX* bnctx;

        bnctx = BN_CTX_new();
        BN_mod_exp(ret._bn, _bn, bn1._bn, bn2._bn, bnctx);
        BN_CTX_free(bnctx);

        return ret;
    }

    int BigNumber::getNumBytes(void)
    {
        return BN_num_bytes(_bn);
    }

    uint32_t BigNumber::asUInt32()
    {
        return (std::uint32_t)BN_get_word(_bn);
    }

    bool BigNumber::isZero() const
    {
        return BN_is_zero(_bn) != 0;
    }

    uint8_t* BigNumber::asByteArray(int minSize)
    {
        int length = SHARED_MAX(minSize, getNumBytes());
        _array.reset(new std::uint8_t[length]);
        SHARED_ASSERT(_array);
        if (!_array) {
            return NULL;
        }
        if (length > getNumBytes()) {
            memset((void*)_array.get(), 0, length);
        }
        BN_bn2bin(_bn, (unsigned char*)_array.get());
        std::reverse(_array.get(), _array.get() + length);
        return _array.get();
    }

    const char* BigNumber::asHexStr()
    {
        return BN_bn2hex(_bn);
    }

    const char* BigNumber::asDecStr()
    {
        return BN_bn2dec(_bn);
    }

}}